# 监督学习基础

"""
mglearn是一个Python库，专门设计用于帮助展示机器学习算法的工作方式。
 它提供了多种数据集和函数，可以用于可视化和解释机器学习模型。
mglearn的目标是简化机器学习的学习曲线，让初学者更容易上手。
 该库的特点包括：

易于使用：mglearn提供了易于理解和使用的函数和工具，使得机器学习的概念和算法更加直观和可视化。
示例丰富：mglearn中包含了大量的示例代码，涵盖了各种常见的机器学习任务，如分类、回归、聚类等。这些示例代码可以帮助用户快速上手和理解机器学习的应用。
可视化工具：mglearn提供了一些可视化工具，如绘制决策边界、绘制特征重要性等，帮助用户更好地理解机器学习算法的工作原理。
与scikit-learn的兼容性：mglearn与scikit-learn库完美兼容，可以与scikit-learn的函数和模型进行无缝集成，扩展了scikit-learn的功能。

"""

import matplotlib.pyplot as plt
import mglearn.datasets
import numpy as np

# 生成分类任务的数据集
# X 是一个二维数组（通常是NumPy数组），其中每一行代表一个样本，每一列代表一个特征。
# 在这个合成数据集中，X 包含了两个特征，因此是一个形状为 (n_samples, 2) 的数组.
# y 是一个一维数组，包含了每个样本的类别标签。在这个数据集中，y 通常包含两个类别标签，用于表示样本属于哪个类别.
# 这个数据集常用于可视化分类模型的决策边界，以及比较不同分类算法的性能
X,y = mglearn.datasets.make_forge()
# 数据集绘图
# 使用 mglearn 库的 discrete_scatter 函数来绘制一个离散散点图，显示数据集 X 中的数据点
# 其中 X[:,0] 和 X[:,1] 分别代表数据点的第一个和第二个特征。
# y=y 参数根据数据点的类别对点进行着色。
mglearn.discrete_scatter(X[:,0], X[:,1], y=y)
plt.legend(["Class 0","Class 1"],loc=4)
plt.xlabel("First feature")
plt.ylabel("Second feature")
print(f"X.shape: {X.shape}")
# 显示图片
# plt.show()

'''
 下面使用模拟的 wave 数据集来说明回归算法。该数据集包含一个特征和一个连续目标变量，
 目标变量是模型预测的对象。图像显示特征在x轴，目标变量在y轴。
'''

# 生成一个模拟的wave（波形）数据集
# n_samples=40参数指定了数据集中样本的数量为40。
# 函数返回两个值：X（特征数据）和y（目标变量）
# X 是一个二维数组（通常是NumPy数组），其中每一行代表一个样本，每一列代表一个特征。
#  在这个合成数据集中，X 只有一个特征，因此是一个形状为 (n_samples, 1) 的数组.
# y 是一个一维数组，包含了每个样本的目标值（即回归任务中的连续值）.
# n_samples=40 是一个参数，指定了生成的数据集中样本的数量为40个.
X, y = mglearn.datasets.make_wave(n_samples=40)
# 'o'参数指定了数据点的标记样式为圆圈。
plt.plot(X,y,'o')
# 这行代码设置y轴的显示范围为-3到3。这有助于确保所有的数据点都能在图中清晰显示。
plt.ylim(-3,3)
plt.xlabel("Feature")
plt.ylabel("Target")
# plt.show()

'''
 之所以使用简单的低维数据集是因为它们容易可视化，而高维数据集的特征超过两个，难以在二维平面上展示。低维数据集的结论可能不适用于高维数据集。
'''

# 下面是以威斯康星州乳腺癌数据集为例的，其任务是基于人体组织的测量数据来学习预测肿瘤是否为恶性。
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
print(f"cancer.keys(): \n{cancer.keys()}")

'''
 Scikit-learn中的数据集通常以Bunch对象形式保存，包含数据和相关信息。
 Bunch对象类似于字典，但可以通过点操作符（如bunch.key）访问值，而不需要使用方括号（如bunch['key']）
'''

# 这个数据集共包含 569 个数据点，每个数据点有 30 个特征：
print(f"Shape of cancer data: {cancer.data.shape}")

# 在 569 个数据点中，212 个被标记为恶性，357 个被标记为良性：
print("Sample counts per class:\n{}".format(
 {n: v for n, v in zip(cancer.target_names, np.bincount(cancer.target))}))

# 为了得到每个特征的语义说明，我们可以看一下 feature_names 属性：
print(f"Feature names:\n{cancer.feature_names}")

from sklearn.model_selection import train_test_split
X, y = mglearn.datasets.make_forge()

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_train, y_train)
print("Test set predictions: {}".format(clf.predict(X_test)))
print("Test set accuracy: {:.2f}".format(clf.score(X_test, y_test)))

fig, axes = plt.subplots(1, 3, figsize=(10, 3))
for n_neighbors, ax in zip([1, 3, 9], axes):
 # fit方法返回对象本身，所以我们可以将实例化和拟合放在一行代码中
 clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X, y)
 # plot_2d_separator 函数用于在二维特征空间中绘制分类器的决策边界。
 # 决策边界是分类器用来区分不同类别的边界，通常在二维空间中表现为一条线或曲线。
 # clf: 这是已经训练好的分类器对象。
 # X: 这是一个二维数组，包含了用于训练分类器的特征数据。
 # fill=True: 这个参数指示是否填充决策区域。如果设置为 True，则在决策边界的不同区域会根据预测的类别填充不同的颜色，使得不同类别的区域更加直观.
 # eps=0.5: 这个参数指定了在特征空间的每个方向上扩展的范围。eps 值越大，绘制的决策边界区域就会在原始数据范围之外扩展得更远，这有助于更好地展示模型的决策行为.
 # ax=ax: 这个参数指定了绘图的轴对象
 # alpha=.4: 这个参数指定了填充区域的透明度。
 mglearn.plots.plot_2d_separator(clf, X, fill=True, eps=0.5, ax=ax, alpha=.4)
 mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)
 ax.set_title("{} neighbor(s)".format(n_neighbors))
 ax.set_xlabel("feature 0")
 ax.set_ylabel("feature 1")
# 为第一个子图添加一个位于左下角的图例,帮助观众更好地理解和分析图中的数据
axes[0].legend(loc=3)
# plt.show()

'''
 随着邻居数量增加，决策边界变得更加平滑，意味着模型更简单。
 使用较少邻居时模型复杂度更高，而使用较多邻居时复杂度降低。
 极端情况下，如果邻居数量等于训练集中的数据点数，所有测试点的预测结果将相同，即训练集中最常见的类别。 
'''

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
# stratify参数在train_test_split函数中用于确保训练集和测试集的划分能够保持原始数据集中各类别的比例。
# 这有助于避免某一类别在训练集或测试集中的样本数量过少，从而影响模型训练和评估的有效性。
# stratify=cancer.target确保了乳腺癌数据集中的恶性和良性肿瘤样本在训练集和测试集中保持相同的比例。
X_train, X_test, y_train, y_test = train_test_split(
 cancer.data, cancer.target, stratify=cancer.target, random_state=66)
train_accuracy = []
test_accuracy = []
# n_neighbors取值从1到10
neighbors_settings = range(1,10)

for n_neighbors in neighbors_settings:
 # 构建模型
 clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X_train, y_train)
 # 记录训练集精度
 train_accuracy.append(clf.score(X_train, y_train))
 # 记录泛化精度
 test_accuracy.append(clf.score(X_test, y_test))

plt.plot(neighbors_settings, train_accuracy, label='train accuracy')
plt.plot(neighbors_settings, test_accuracy, label='test accuracy')
plt.ylabel('Accuracy')
plt.xlabel('n_neighbors')
plt.legend()
plt.show()

'''
 x轴表示邻居数，y轴表示训练和测试精度。
 当邻居数很少时，模型复杂度高，训练精度高但测试精度低，表现为过拟合。
 随着邻居数增加，模型简单化，训练精度下降，测试精度上升。
 邻居数过多时，模型过于简单，性能下降。
 最佳性能出现在邻居数约为6时。即使最差性能也有88%的精度，这是可以接受的。
'''